StreamingTool

abstract class StreamingTool<TInput, TOutput, TContext>(name: String, description: String, inputSerializer: KSerializer<TInput>, outputSerializer: KSerializer<TOutput>, strict: Boolean = true, inputExamples: List<String> = emptyList(), metadata: Map<String, JsonElement> = emptyMap(), providerExecuted: Boolean = false, providerOptions: Map<String, JsonElement> = emptyMap()) : Tool<TInput, TOutput, TContext>

A Tool whose executor is a Flow — it can emit preliminary snapshots before the final value. The LAST emission is what feeds the model on subsequent turns; earlier emissions surface as StreamEvent.ToolResult(preliminary = true) for UI consumption only. Extend this (instead of Tool) when a tool can produce a useful early snapshot — a small summary, count, or status — before the full result is ready.

class LineupTool(private val repo: LineupRepo) :
StreamingTool<LineupQuery, Lineup, AppContext>(
name = "getLineup",
description = "Get sets playing at a stage on a given day",
inputSerializer = serializer(),
outputSerializer = serializer(),
) {
override fun ToolExecutionContext<AppContext>.executeStream(input: LineupQuery) = flow {
emit(repo.fastSummary(input)) // preliminary — UI renders immediately
emit(repo.fullDetails(input)) // final — feeds the model
}
}

If the Flow emits zero values, the tool is treated as failed (the agent emits StreamEvent.ToolError).

Constructors

Link copied to clipboard
constructor(name: String, description: String, inputSerializer: KSerializer<TInput>, outputSerializer: KSerializer<TOutput>, strict: Boolean = true, inputExamples: List<String> = emptyList(), metadata: Map<String, JsonElement> = emptyMap(), providerExecuted: Boolean = false, providerOptions: Map<String, JsonElement> = emptyMap())

Properties

Link copied to clipboard
Link copied to clipboard

Few-shot example inputs encoded as JSON strings. Mirrors v6's tool.inputExamples (per historical parity gap #19). Disproportionate quality lift on small models — Gemma 4 E2B's tool-call accuracy notably improves with 2-3 in-tool examples. ToolSet.descriptors inlines these into the description as Example: <json> so every provider sees them (most don't expose a separate examples field).

Link copied to clipboard
val inputSerializer: KSerializer<TInput>
Link copied to clipboard
val metadata: Map<String, JsonElement>

Application-defined metadata bag. Mirrors v6's tool.metadata (per historical parity gap #34). Opaque to the loop — consumers (logger middleware, telemetry, host-side gating) can read it via ToolSet.byName. Examples: feature-flag key, billing tier, owning team. Empty by default.

Link copied to clipboard
Link copied to clipboard
val outputSerializer: KSerializer<TOutput>
Link copied to clipboard
Link copied to clipboard
val providerOptions: Map<String, JsonElement>

Provider-specific config for this tool, sent to the model on the wire (e.g. Anthropic cache_control). Distinct from metadata, which is host-side only. Mirrors v6's tool.providerOptions. Empty by default.

Link copied to clipboard

Per best practice #9: strict JSON schema enforcement is opt-in per tool, not global. Some providers reject schemas with format/regex constraints under strict mode; mark such tools strict = false.

Functions

Link copied to clipboard
suspend override fun ToolExecutionContext<TContext>.execute(input: TInput): TOutput

Run the tool — the common single-value case. The model's JSON input has already been deserialized into TInput; return the typed output. this is the ToolExecutionContext, so the typed context, abortSignal, toolCallId, stepNumber, messages, and writer are in scope.

Link copied to clipboard

Run the tool, emitting preliminary snapshots; the LAST emission is the final result. this is the ToolExecutionContext.

Link copied to clipboard
open suspend fun needsApproval(input: TInput, options: ToolPredicateOptions<TContext>): Boolean

Approval gate (parity gap #17). Return true to require host approval before execute runs: the loop ends with the call surfaced in GenerateResult.pendingApprovals until the host resumes. The options carry toolCallId + messages + experimental_context, so the gate can decide from conversation history or call identity. Default: never gates.

Link copied to clipboard
open suspend fun onInputAvailable(toolCallId: String, input: TInput)

Lifecycle hook — fired after the streamed JSON parses into the typed TInput, just before execute runs. Mirrors v6's tool.onInputAvailable. No-op by default.

Link copied to clipboard
open suspend fun onInputDelta(streamingId: String, delta: String)

Lifecycle hook (gap #18) — fired as the input JSON streams in token-by-token (StreamEvent.ToolInputDelta). The accumulated input isn't yet valid JSON; this is for raw-character pre-warm. No-op by default.

Link copied to clipboard
open suspend fun onInputStart(streamingId: String)

Lifecycle hook (gap #18) — fired when the model commits to calling this tool (StreamEvent.ToolInputStart). Pre-warm here: UI spinner, cache priming, etc. No-op by default.

Link copied to clipboard

Model-visible summary (parity gaps #17 + #14). Return a structured ToolResultOutput (Text / Json / Error / …) to send the model a short summary in place of the full (possibly verbose) output; the loop bridges it to ContentPart.ToolResult.modelVisible (+ isError). Return null (default) to send the raw output as-is.